//
//  AWSQueryConnection.m
//  cTypica
//
//  Created by David Kavanagh on 10/14/08.
//  Copyright 2008 directThought. All rights reserved.
//

#import "AWSQueryConnection.h"
#import <Foundation/Foundation.h>
#import <CFNetwork/CFHTTPStream.h>
#import <Foundation/NSCharacterSet.h>
#import "OAHMAC_SHA1SignatureProvider.h"
#import "LGXMLParser.h"
#import "ErrorDelegate.h"

@implementation AWSQueryConnection
@synthesize accessId;
@synthesize secretKey;
@synthesize hostName;
@synthesize maxRetries;
@synthesize errorDelegate;

- (id)initWithAccessId:(NSString *)aId secretKey:(NSString *)sKey hostName:(NSString *)hName {
	self.accessId = aId;
	self.secretKey = sKey;
	self.hostName = hName;
	formatter = [[NSDateFormatter alloc] init];
	[formatter setDateFormat:@"%Y-%m-%dT%H:%M:%SZ"];
	return self;
}

- (void) dealloc {
	[formatter release];
	[super dealloc];
}

- (void)makeRequest:(NSString *)action parameters:(NSDictionary *)params delegate:(id)delegate {
	NSLog(@"in makeRequest %@", action);
	if (self.accessId != nil && self.secretKey != nil) {
		// build string to sign
		NSDate *theDate = [NSDate date];
		//NSString *reqTime = [formatter stringFromDate:theDate];
		NSString *reqTime = [theDate descriptionWithCalendarFormat:@"%Y-%m-%dT%H:%M:%SZ" timeZone:[NSTimeZone timeZoneWithName:@"GMT"] locale:nil];
		NSLog(@"formatted date is %@",reqTime);
		//[theDate release];
		NSMutableDictionary *allParams = [[[NSMutableDictionary alloc] init] autorelease];
		[allParams addEntriesFromDictionary:params];
		[allParams setValue:action forKey:@"Action"];
		[allParams setValue:self.accessId forKey:@"AWSAccessKeyId"];
		[allParams setValue:@"1" forKey:@"SignatureVersion"];
		[allParams setValue:reqTime	forKey:@"Timestamp"];
		[allParams setValue:version forKey:@"Version"];
		NSMutableString *signString = [NSMutableString stringWithCapacity:256];
		NSEnumerator *keys = [[[allParams allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectEnumerator];
		NSString *key;
		while (key = [keys nextObject]) {
			[signString appendFormat:@"%@%@", key, [allParams valueForKey:key]];
		}
	
		// sign it
		NSLog(@"String to sign %@", signString);
		OAHMAC_SHA1SignatureProvider *provider = [[OAHMAC_SHA1SignatureProvider alloc] init];
		NSString *sig = [provider signClearText:signString withSecret:self.secretKey];

		// build signed request string
		NSString *uReqTime = [self encodedURLParameterString:reqTime];
		//[reqTime release];
		[allParams setValue:uReqTime forKey:@"Timestamp"];
		[uReqTime release];
		NSString *usig = [self encodedURLParameterString:sig];
		[sig release];
		[allParams setValue:usig forKey:@"Signature"];
		NSMutableString *signedString = [NSMutableString stringWithCapacity:256];
		keys = [[[allParams allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectEnumerator];
		while (key = [keys nextObject]) {
			[signedString appendFormat:@"&%@=%@", key, [allParams valueForKey:key]];
			if ([key isEqualToString:@"GroupDescription"]) {
				NSLog(@"key %@, encoded %@", key, [self encodedURLParameterString:key]);
			}
		}
		[signedString replaceCharactersInRange:NSMakeRange(0, 1) withString:@"?"];
		NSLog(@"signedString %@", signedString);
		
		NSString *urlString = [NSString stringWithFormat:@"https://%@/", hostName];
		CFURLRef baseURL = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)urlString, nil);
		CFURLRef reqURL = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)signedString, baseURL);
		
		CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), reqURL, kCFHTTPVersion1_1);
		CFHTTPMessageSetHeaderFieldValue(request, CFSTR("User-Agent"), CFSTR("cTypica-pre-alpha"));
		NSLog(@"Making request");

		BOOL done = NO;
		BOOL retry = NO;
		int retries = 0;
		do {
			CFReadStreamRef readStr = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request, nil);
			NSLog(@"Made request");
			CFReadStreamOpen(readStr);
			
			UInt8 buf[16*1024];
			CFIndex bytesRead = CFReadStreamRead(readStr, buf, sizeof(buf));
			if (bytesRead < 0) {
				[errorDelegate connectionFailed:@"Could not communicate with the server."];
				return;
			}
			CFHTTPMessageRef myResponse = CFReadStreamCopyProperty(readStr, kCFStreamPropertyHTTPResponseHeader);
			UInt32 responseCode = CFHTTPMessageGetResponseStatusCode(myResponse);
			if (responseCode < 300) { // 200's : parse normal response		LGXMLParser *xmlParser = [LGXMLParser alloc];
				LGXMLParser *xmlParser = [LGXMLParser alloc];
				[xmlParser setDelegate:delegate];
				while (bytesRead > 0) {
					//NSLog(@"data read %@", [[[NSString alloc] initWithBytes:buf length:bytesRead encoding:0] autorelease]);
					[xmlParser parseData:[NSData dataWithBytes:buf length:bytesRead]];
					bytesRead = CFReadStreamRead(readStr, buf, sizeof(buf));
				}
				[xmlParser finish];
				done = YES;
			}
			else if (responseCode < 400) { // 300's : what to do?
			}
			else if (responseCode < 500) { // 400's : parse client error messages
				ErrorDelegate *errDelegate = [ErrorDelegate delegate];
				LGXMLParser *xmlParser = [LGXMLParser alloc];
				[xmlParser setDelegate:errDelegate];
				[xmlParser parseData:[NSData dataWithBytes:buf length:bytesRead]];
				[xmlParser finish];

				[errorDelegate webServiceError:[errDelegate message] errorCode:[errDelegate code] reqId:[errDelegate requestId]];
				done = YES;
			}
			else if (responseCode < 600) { // 500's : retry...
				retry = YES;
			}
			CFReadStreamClose(readStr);
			//CFRelease(readStr);
			if (retry) {
				retries++;
				if (retries > maxRetries) {
					NSString *errCode = [[[NSString alloc] initWithFormat:@"%@", responseCode] autorelease];
					[errorDelegate webServiceError:@"Number of retries exceeded" errorCode:errCode reqId:nil];
				}
				retry = NO;
				[NSThread sleepForTimeInterval:(retries * 2)];
			}
		} while (!done);
//		CFRelease(request);
//		CFRelease(reqURL);
		return;
	}
	return;
}

- (NSString *)encodedURLParameterString:(NSString *)val {
    return (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                           (CFStringRef)val,
                                                                           NULL,
                                                                           CFSTR(":/= +*"),
                                                                           kCFStringEncodingUTF8);
}

@end
